package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.changgou.pojo.SkuInfo;
import com.changgou.search.service.EsSearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <dl>
 * <dd>描述: ~节点业务逻辑实现</dd>
 * <dd>创建时间：  9:47 2020/4/6</dd>
 * <dd>创建人： zz</dd>
 * <dt>版本历史: </dt>
 * <pre>
 * Date         Author      Version     Description
 * ------------------------------------------------------------------
 * 2020/4/6      guodong       1.0        1.0 Version
 * </pre>
 * </dl>
 */
@Service
public class EsSearchServiceImpl implements EsSearchService {

    @Autowired
    private ElasticsearchTemplate template;


    /**
     * @Date: 9:48 2020/4/6
     * @Param: [searchMap]
     * @return: com.changgou.entity.Result
     * @Description: 用户搜索
     **/
    @Override
    public Map<String, Object> search(Map<String, String> searchMap) {
        Map<String, Object> resultMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(searchMap)) {

            //组合查询对象
            BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
            /*
                需求一： 根据关键字模糊搜索
                        默认返回10条结果数据
            */
            String keywords = searchMap.get("keywords");
            if (StringUtils.isNotEmpty(keywords)) {
                //and or(默认)
                boolQueryBuilder.must(QueryBuilders.matchQuery("name", keywords).operator(Operator.AND));
            }

            /*
                需求五： 根据品牌名称精确查询 brandName
             */
            String brandName = searchMap.get("brand");
            if (StringUtils.isNotEmpty(brandName)) {
                boolQueryBuilder.must(QueryBuilders.termQuery("brandName", brandName));
                /*
                    must： 搜索匹配条件的文档并打分
                    filter： 搜索匹配条件的文档，不会打分
                    两者效率比较,filter执行效率更快
                 */
                //boolQueryBuilder.filter(QueryBuilders.termQuery("brandName", brandName));
            }

            /*
                需求六： 根据分类名称精确查询 categoryName
             */
            String categoryName = searchMap.get("categoryName");
            if (StringUtils.isNotEmpty(categoryName)) {
                boolQueryBuilder.must(QueryBuilders.termQuery("categoryName", categoryName));
            }

            /*
                需求七： 根据规格精确查询
                               需要特殊处理
                                        spec_颜色=红色
                                        spec_内存=6G+128G --->字符串截取即可

                                        由于使用term查询需要字段类型为 keyword

             */
            for (Map.Entry<String, String> entry : searchMap.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                //specMap.颜色
                if (key.startsWith("spec_")) {
                    String spec = "specMap." + key.substring(5) + ".keyword";
                    boolQueryBuilder.must(QueryBuilders.termQuery(spec, value));
                }
            }


            /*
                需求八： 根据价格进行范围查询
                            格式： 100-2000
                            select * from tb_sku where price >=100 and price<=2000
                            大于等于开始价位 gte
                            小于等于结束价位 lte

             */
            String price = searchMap.get("price");
            if (StringUtils.isNotEmpty(price)) {
                String[] split = price.split("-");
                if (2 == split.length) {
                    String beginPrice = split[0];
                    String endPrice = split[1];
                    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(beginPrice).lte(endPrice));
                }

            }

            //原生查询对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            nativeSearchQueryBuilder.withQuery(boolQueryBuilder);


             /*
                 需求二： 根据品牌聚合--->设置聚合名称+聚合字段
             */
            String brandNameGroup = "brandNameGroup";
            TermsAggregationBuilder brandGroupBuild = AggregationBuilders.terms(brandNameGroup).field("brandName");
            nativeSearchQueryBuilder.addAggregation(brandGroupBuild);

            /*
                需求三： 根据分类名称聚合--->设置聚合名称+聚合字段

             */

            String cateNameGroup = "cateNameGroup";
            TermsAggregationBuilder cateGroupBuild = AggregationBuilders.terms(cateNameGroup).field("categoryName");
            nativeSearchQueryBuilder.addAggregation(cateGroupBuild);

            /*
                需求四： 根据规格名称聚合---》设置聚合名称+聚合字段
                        注意： 由于spec不是keyword类型，所以在聚合是需要临时设置 ： spec.keyword
             */
            String specGroup = "specGroup";
            TermsAggregationBuilder specGroupBuild = AggregationBuilders.terms(specGroup).field("spec.keyword");
            nativeSearchQueryBuilder.addAggregation(specGroupBuild);

            /*
                需求九： 根据指定字段、指定规则排序
                            sortFiled， sortField  sortRule
             */
            String sortFiled = searchMap.get("sortField"); //排序字段
            String sortRule = searchMap.get("sortRule"); //排序规则
            if (StringUtils.isNotEmpty(sortFiled) && StringUtils.isNotEmpty(sortRule)) {
                if ("DESC".equals(sortRule)) {
                    //equalsIgnoreCase 不区分大小写  equals：区分大小写
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortFiled).order(SortOrder.DESC));
                } else {
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortFiled).order(SortOrder.ASC));
                }
            }

            /*
                需求十： 高亮查询
                            ---》设置高亮字段
                            ---》设置前置标签
                            ---》设置后置标签
             */
            /*HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("name");
            highlightBuilder.preTags("<span color='red'>");
            highlightBuilder.postTags("</span>");*/

            HighlightBuilder.Field highlightField = new HighlightBuilder
                    .Field("name") //设置高亮字段
                    .preTags("<span color='red'>") //设置前置标签
                    .postTags("</span>"); //设置后置标签
            nativeSearchQueryBuilder.withHighlightFields(highlightField);


            /*
               需求十一： 分页操作
                            ----》pageNum : 页码
                                  pageSize: 每页显示多少条
             */

            int pageNum = 1; //默认页码
            int pageSize = 20; // 默认每页显示多少条
            String pageNumStr = searchMap.get("pageNum");
            String pageSizeStr = searchMap.get("pageSize");
            if(StringUtils.isNotEmpty(pageNumStr)){
                pageNum = Integer.valueOf(pageNumStr);
            }
            if(StringUtils.isNotEmpty(pageSizeStr)){
                pageSize = Integer.valueOf(pageSizeStr);
            }
            //PageRequest.of(page,size) 需要注意page： 0 代表第一页  1；代表第二页 以此类推
            nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNum-1,pageSize));

            //查询
            AggregatedPage<SkuInfo> aggregatedPage = template.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                    SearchHits hits = response.getHits();
                    List<T> list = new ArrayList<>();
                    if (null != hits) {
                        for (SearchHit hit : hits) {
                            String skuInfoJson = hit.getSourceAsString();
                            SkuInfo skuInfo = JSONObject.parseObject(skuInfoJson, SkuInfo.class);
                             /*
                                 需求十： 高亮结果处理
                            */
                            //高亮结果解析
                            String name = hit.getHighlightFields().get("name").getFragments()[0].toString();
                            //结果设值
                            skuInfo.setName(name);

                            list.add((T) skuInfo);
                        }
                        /*
                            第一个参数： 查询结果列表
                            第二个参数： 分页对象
                            第三个参数： 查询结果总记录数
                            第四个参数： 分组聚合结果
                         */
                        return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), response.getAggregations());
                    }
                    return new AggregatedPageImpl<T>(list);
                }
            });

             /*
                需求二： 根据品牌聚合--->结果解析
             */
            StringTerms brandTerms = (StringTerms) aggregatedPage.getAggregation(brandNameGroup);
            List<StringTerms.Bucket> brandTermsBuckets = brandTerms.getBuckets();
            List<String> brandList = new ArrayList<>();
            if (!CollectionUtils.isEmpty(brandTermsBuckets)) {
                for (StringTerms.Bucket brandTermsBucket : brandTermsBuckets) {
                    brandList.add(brandTermsBucket.getKeyAsString());
                }
            }

             /*
                需求三： 根据分类名称聚合--->结果解析

             */
            List<String> cateList = new ArrayList<>();
            StringTerms cateStringTerms = (StringTerms) aggregatedPage.getAggregation(cateNameGroup);
            List<StringTerms.Bucket> cateTermsBuckets = cateStringTerms.getBuckets();
            if (!CollectionUtils.isEmpty(cateTermsBuckets)) {
                cateList = cateTermsBuckets.stream().map(StringTerms.Bucket::getKeyAsString).collect(Collectors.toList());
            }

            /*
                需求四： 根据规格名称聚合---》结果解析
             */

            //存在问题的specList
           /* List<String> specList = new ArrayList<>();
            StringTerms specStringTerms = (StringTerms) aggregatedPage.getAggregation(specGroup);
            List<StringTerms.Bucket> specBuckets = specStringTerms.getBuckets();
            if(!CollectionUtils.isEmpty(specBuckets)){
                //specList = specBuckets.stream().map(StringTerms.Bucket::getKeyAsString).collect(Collectors.toList());
                for (StringTerms.Bucket specBucket : specBuckets) {
                    String spec = specBucket.getKeyAsString();
                    specList.add(spec);
                }
            }*/

            //对specList做特殊处理
            Map<String, Set<String>> specList = new HashMap<>();
            StringTerms specStringTerms = (StringTerms) aggregatedPage.getAggregation(specGroup);
            List<StringTerms.Bucket> specBuckets = specStringTerms.getBuckets();
            if (!CollectionUtils.isEmpty(specBuckets)) {
                //specList = specBuckets.stream().map(StringTerms.Bucket::getKeyAsString).collect(Collectors.toList());
                for (StringTerms.Bucket specBucket : specBuckets) {

                    /*
                        "{'颜色': '蓝色', '版本': '6GB+128GB'}",
                        "{'颜色': '蓝色', '版本': '4GB+64GB'}",
                        "{'颜色': '蓝色', '版本': '6GB+64GB'}",

                        "{'颜色': '黑色', '版本': '6GB+128GB'}",
                        "{'颜色': '黑色', '版本': '4GB+64GB'}"
                        需求： key 不可重复
                              value 不可以重复
                              Map<String, Set<String>> specMap = new HashMap<>();
                              String spec = "{'颜色': '蓝色', '版本': '6GB+128GB'}";

                     */
                    String spec = specBucket.getKeyAsString();
                    Map<String, String> specMap = JSONObject.parseObject(spec, Map.class);
                    for (Map.Entry<String, String> entry : specMap.entrySet()) {
                        //第一种方式
                        Set<String> specSet = new HashSet<>();
                        String key = entry.getKey();
                        String value = entry.getValue();
                        if (specList.containsKey(key)) { //通过map key属性确保 key值不重复
                            specSet = specList.get(key);
                        }
                        //通过set属性确保val不重复
                        specSet.add(value);
                        //将结果添加至specList
                        specList.put(key, specSet);

                        //方式二
                      /*if(specList.containsKey(key)){
                          Set<String> specSet = specList.get(key);
                          specSet.add(value);
                          specList.put(key, specSet);
                      }else{
                          Set<String> specSet = new HashSet<>();
                          specSet.add(value);
                          specList.put(key, specSet);
                      }*/
                    }
                }
            }

            //获取结果
            resultMap.put("total", aggregatedPage.getTotalElements()); //总记录数
            resultMap.put("totalPages", aggregatedPage.getTotalPages()); //总页数
            resultMap.put("rows", aggregatedPage.getContent()); //查询结果列表
            resultMap.put("brandList", brandList); //品牌聚合结果列表
            resultMap.put("cateList", cateList); //分类聚合结果列表
            resultMap.put("specList", specList); //规格聚合结果列表
            resultMap.put("pageNum", pageNum); //页码
            resultMap.put("pageSize", pageSize);
        }
        return resultMap;
    }
}
